package com.itheima.project.face.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.itheima.project.basic.ResponseWrap;
import com.itheima.project.binding.CartSource;
import com.itheima.project.constant.AppletCacheConstant;
import com.itheima.project.constant.ShopConstant;
import com.itheima.project.constant.SuperConstant;
import com.itheima.project.constant.trade.TradingConstant;
import com.itheima.project.enums.*;
import com.itheima.project.exception.ProjectException;
import com.itheima.project.face.AppletFace;
import com.itheima.project.feign.DataDictFeign;
import com.itheima.project.feign.FileFeign;
import com.itheima.project.pojo.*;
import com.itheima.project.service.*;
import com.itheima.project.utils.BeanConv;
import com.itheima.project.utils.EmptyUtil;
import com.itheima.project.utils.ExceptionsUtil;
import com.itheima.project.vo.*;
import com.itheima.project.vo.dict.DataDictVo;
import com.itheima.project.vo.file.FileVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RLock;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
 * @ClassName AppletFaceImpl.java
 * @Description 小程序H5实现
 */
@Slf4j
@Service
public class AppletFaceImpl implements AppletFace {
    @Autowired
    private FileFeign fileFeign;
    @Autowired
    private DataDictFeign dataDictFegin;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    IOrderService orderService;
    @Autowired
    ITableService tableService;
    @Autowired
    IOrderItemService orderItemService;
    @Autowired
    IdentifierGenerator identifierGenerator;
    @Autowired
    ICategoryService categoryService;
    @Autowired
    IDishService dishService;
    @Autowired
    IDishFlavorService dishFlavorService;
    @Autowired
    BrandService brandService;
    @Autowired
    IStoreService storeService;

    @Override
    public Boolean isOpen(Long tableId) throws ProjectException {
        try {
            // 1. 查询桌台信息，是否为使用中
            Table table = tableService.getById(tableId);
            boolean isUse = ShopConstant.USE.equals(table.getTableStatus());
            // 2. 查询是否有【支付中   待付款的订单】
            OrderVo orderVo = orderService.findOrderByTableId(tableId);
            boolean flagOrderVo = !EmptyUtil.isNullOrEmpty(orderVo);
            return isUse || flagOrderVo;
//            //1、查询桌台信息，是否为使用中
//            Table table = tableService.getById(tableId);
//            Boolean flagTableStatus = table.getTableStatus().equals(ShopConstant.USE);  // true use   false free
//            //2、是否已经有【待支付、支付中】订单存在
//            OrderVo orderVoResult = orderService.findOrderByTableId(tableId);  // !null use
//            //3. 判断是否查询到 对应订单 true: 查询到  false: 未查询到
//            Boolean flagOrderVo = !EmptyUtil.isNullOrEmpty(orderVoResult);
//            // 如果状态为 使用 或 有相关订单信息  代表已经开台
//            return flagTableStatus || flagOrderVo;
        } catch (Exception e) {
            log.error("查询桌台信息异常：{}", ExceptionsUtil.getStackTraceAsString(e));
            throw new ProjectException(TableEnum.SELECT_TABLE_FAIL);
        }
    }

    @Override
    public AppletInfoVo findAppletInfoVoByTableId(Long tableId) throws ProjectException {
        try {
            // 1. 查询桌台信息
            Table table = tableService.getById(tableId);
            TableVo tableVo = BeanConv.toBean(table, TableVo.class);
            // 2. 查询门店信息
            Store store = storeService.getById(tableVo.getStoreId());
            StoreVo storeVo = BeanConv.toBean(store, StoreVo.class);
            // 3. 查询所属品牌
            Brand brand = brandService.getById(storeVo.getBrandId());
            BrandVo brandVo = BeanConv.toBean(brand, BrandVo.class);
            // 4. 查询品牌对应图片
            ResponseWrap<List<FileVo>> fileVoByBusinessId = fileFeign.findFileVoByBusinessId(brandVo.getId());
            if (!EmptyUtil.isNullOrEmpty(fileVoByBusinessId)) {
                brandVo.setFileVo(fileVoByBusinessId.getDatas().get(0));
            }
            // 5. 查询门店下的菜品分类列表
            List<Category> categoryList = categoryService.findCategoryVoByStoreId(store.getId());
            List<CategoryVo> categoryVoList = BeanConv.toBeanList(categoryList, CategoryVo.class);
            // 6. 查询门店下的菜品列表
            List<Dish> dishList = dishService.findDishVoByStoreId(store.getId());
            List<DishVo> dishVoList = BeanConv.toBeanList(dishList, DishVo.class);
            // 7. 遍历菜品 封装: 菜品口味dataKeys    封装菜品口味字段信息
            dishVoList.forEach(dishVo -> {
                // 7.1 中间表 查询到菜品口味中间表 == 口味dataKeys列表
                List<DishFlavor> dishFlavorList = dishFlavorService.findDishFlavorByDishId(dishVo.getId());
                List<DishFlavorVo> dishFlavorVos = BeanConv.toBeanList(dishFlavorList, DishFlavorVo.class);
                dishVo.setDishFlavorVos(dishFlavorVos);
                // 7.2 拿到dataKeys 使用数据字段feign接口查询字典信息
                List<String> dataKeys = dishFlavorList.stream().map(DishFlavor::getDataKey).collect(Collectors.toList());
                ResponseWrap<List<DataDictVo>> responseWrap = dataDictFegin.findValueByDataKeys(new ArrayList<>(dataKeys));
                if (!EmptyUtil.isNullOrEmpty(responseWrap)) {
                    dishVo.setDataDictVos(responseWrap.getDatas());
                }
                // 7.3 查询菜品对应附件
                ResponseWrap<List<FileVo>> fileResponseWrap = fileFeign.findFileVoByBusinessId(dishVo.getId());
                if (!EmptyUtil.isNullOrEmpty(fileResponseWrap)) {
                    dishVo.setFileVo(fileResponseWrap.getDatas().get(0));
                }
            });
            //8. 将所有信息封装到主题信息
            return AppletInfoVo.builder()
                    .brandVo(brandVo)
                    .tableVo(tableVo)
                    .categoryVos(categoryVoList)
                    .dishVos(dishVoList)
                    .storeVo(storeVo)
                    .build();
        } catch (Exception e) {
            log.error("查询桌台相关主体信息异常：{}", ExceptionsUtil.getStackTraceAsString(e));
            throw new ProjectException(TableEnum.SELECT_TABLE_FAIL);
        }
    }

    //    @Override
//    public AppletInfoVo findAppletInfoVoByTableId(Long tableId)throws ProjectException {
//        try {
//            // 1. 查询桌台信息
//            Table table = tableService.getById(tableId);
//            TableVo tableVo = BeanConv.toBean(table, TableVo.class);
//
//            // 2. 查询门店信息
//            Store store = storeService.getById(table.getStoreId());
//            StoreVo storeVo = BeanConv.toBean(store, StoreVo.class);
//
//            // 3. 查询所属品牌
//            Brand brand = brandService.getById(store.getBrandId());
//            BrandVo brandVo = BeanConv.toBean(brand, BrandVo.class);
//
//            // 4. 查询品牌对应图片
//            ResponseWrap<List<FileVo>> fileResult = fileFeign.findFileVoByBusinessId(brand.getId());
//            if (!EmptyUtil.isNullOrEmpty(fileResult)) {
//                brandVo.setFileVo(fileResult.getDatas().get(0));
//            }
//
//            // 5. 查询门店下的菜品分类列表
//            List<Category> categorys = categoryService.findCategoryVoByStoreId(table.getStoreId());
//            List<CategoryVo> categoryVoList = BeanConv.toBeanList(categorys, CategoryVo.class);
//
//            // 6. 查询门店下的菜品列表
//            List<Dish> dishs = dishService.findDishVoByStoreId(table.getStoreId());
//            List<DishVo> dishVos = BeanConv.toBeanList(dishs, DishVo.class);
//
//            // 7. 遍历菜品 封装: 菜品口味dataKeys    封装菜品口味字段信息
//            dishVos.forEach(dishVo->{
//
//                // 7.1 中间表 查询到菜品口味中间表 == 口味dataKeys列表
//                List<DishFlavor> dishFlavors = dishFlavorService.findDishFlavorByDishId(dishVo.getId());
//                List<DishFlavorVo> dishFlavorVos = BeanConv.toBeanList(dishFlavors, DishFlavorVo.class);
//                dishVo.setDishFlavorVos(dishFlavorVos);
//
//                // 7.2 拿到dataKeys 使用数据字段feign接口查询字典信息
//                List<String> dataKeys = dishFlavorVos.stream()
//                        .map(DishFlavorVo::getDataKey).collect(Collectors.toList());
//                ResponseWrap<List<DataDictVo>> result = dataDictFegin.findValueByDataKeys(new ArrayList<>(dataKeys));
//                if (!EmptyUtil.isNullOrEmpty(result)) {
//                    dishVo.setDataDictVos(result.getDatas());
//                }
//
//                // 7.3 查询菜品对应附件
//                ResponseWrap<List<FileVo>> dishFileResult = fileFeign.findFileVoByBusinessId(dishVo.getId());
//                if (!EmptyUtil.isNullOrEmpty(dishFileResult)) {
//                    dishVo.setFileVo(dishFileResult.getDatas().get(0));
//                }
//            });
//
//            //8. 将所有信息封装到主题信息
//            AppletInfoVo appletInfoVo = AppletInfoVo.builder()
//                    .tableVo(tableVo)
//                    .storeVo(storeVo)
//                    .brandVo(brandVo)
//                    .categoryVos(categoryVoList)
//                    .dishVos(dishVos)
//                    .build();
//            return appletInfoVo;
//        } catch (Exception e) {
//            log.error("查询桌台相关主体信息异常：{}", ExceptionsUtil.getStackTraceAsString(e));
//            throw new ProjectException(TableEnum.SELECT_TABLE_FAIL);
//        }
//    }
    @Override
    @Transactional
    public OrderVo openTable(Long tableId, Integer personNumbers) throws ProjectException {
        //2、锁定桌台，防止并发重复创建订单？

        // 锁定桌台： 分布式锁的key
        String key = AppletCacheConstant.OPEN_TABLE_LOCK + tableId;

        // redisson获取分布式所
        RLock lock = redissonClient.getLock(key);

        try {
            // 加锁: 非阻塞加锁
            if (lock.tryLock(AppletCacheConstant.REDIS_WAIT_TIME,  // 加锁等待时间
                    AppletCacheConstant.REDIS_LEASETIME, // 锁的失效时间
                    TimeUnit.MINUTES)) { // 时间单位

                //3、幂等性：再次查询桌台订单情况？
                OrderVo orderVoResult = orderService.findOrderByTableId(tableId);

                //4、未开台,为桌台创建当订单
                if (EmptyUtil.isNullOrEmpty(orderVoResult)) {
                    //4.1、查询桌台信息
                    Table table = tableService.getById(tableId);
                    //4.2、构建订单
                    Order order = Order.builder()
                            .tableId(tableId)
                            .tableName(table.getTableName())
                            .storeId(table.getStoreId())
                            .areaId(table.getAreaId())
                            .enterpriseId(table.getEnterpriseId())
                            .orderNo((Long) identifierGenerator.nextId(tableId))  // 订单编号*** 雪花算法id
                            .orderState(TradingConstant.DFK) // 订单状态 DFK
                            .isRefund(SuperConstant.NO) // 是否退款
                            .refund(new BigDecimal(0)) //退款金额
                            .discount(new BigDecimal(10)) //折扣
                            .personNumbers(personNumbers) //就餐人数
                            .reduce(new BigDecimal(0)) //优惠价格
                            .useScore(0) //积分
                            .acquireScore(0L) //累计积分
                            .build();
                    orderService.save(order);
                    //5、修改桌台状态为使用中
                    TableVo tableVo = TableVo.builder()
                            .id(tableId)
                            .tableStatus(ShopConstant.USE).build();
                    tableService.updateTable(tableVo);
                }
            }
            //6、订单处理：处理可核算订单项和购物车订单项，可调用桌台订单显示接口
            return showOrderVoforTable(tableId);
        } catch (Exception e) {
            log.error("开桌操作异常：{}", ExceptionsUtil.getStackTraceAsString(e));
            throw new ProjectException(TableEnum.OPEN_TABLE_FAIL);
        } finally {
            lock.unlock();
        }
    }

    /**
     * 根据桌台ID查询购物车和订单列表
     *
     * @param tableId 桌台ID
     * @return
     * @throws ProjectException
     */
    @Override
    public OrderVo showOrderVoforTable(Long tableId) throws ProjectException {
        try {

            //查询订单信息
            OrderVo orderVoResult = orderService.findOrderByTableId(tableId);

            //处理订单：可核算订单项目、购物车订单项目
            return handlerOrderVo(orderVoResult);

        } catch (Exception e) {
            log.error("查询桌台订单信息异常：{}", ExceptionsUtil.getStackTraceAsString(e));
            throw new ProjectException(OrderEnum.SELECT_TABLE_ORDER_FAIL);
        }
    }

    /***
     * @description 处理当前订单中订单项
     * 从DB中查询当前订单可核算订单项
     * 从redis查询当前订单购物车订单项
     * @param orderVo 订单信息
     * @return
     */
    @Override
    public OrderVo handlerOrderVo(OrderVo orderVo) throws ProjectException {
        if (!EmptyUtil.isNullOrEmpty(orderVo)) {
            //1、查询MySQL:可核算订单项
            List<OrderItem> orderItemList = orderItemService.findOrderItemByOrderNo(orderVo.getOrderNo());
            List<OrderItemVo> orderItemVoStatisticsList = BeanConv.toBeanList(orderItemList, OrderItemVo.class);

            //2、可核算订单项总金额
            BigDecimal reducePriceStatistics = new BigDecimal("0");
            if (!EmptyUtil.isNullOrEmpty(orderItemVoStatisticsList)) {
                // 2.1 回填订单fileVO数据
                orderItemVoStatisticsList.forEach(n -> {
                    FileVo fileVo = fileFeign.findFileVoByBusinessId(n.getDishId()).getDatas().get(0);
                    n.setFileVo(fileVo);
                });
                //2.2、计算可核算订单项总金额
                reducePriceStatistics = reducePriceHandler(orderItemVoStatisticsList);
            }

            //3、查询redis:购物车订单项
            String key = AppletCacheConstant.ORDERITEMVO_STATISTICS + orderVo.getOrderNo();
            RMapCache<Long, OrderItemVo> orderItemVoRMap = redissonClient.getMapCache(key);
            List<OrderItemVo> orderItemVoTemporaryList = (List<OrderItemVo>) orderItemVoRMap.readAllValues();

            //3.1、计算购物车订单项总金额
            BigDecimal reducePriceTemporary = reducePriceHandler(orderItemVoTemporaryList);

            //4、构建订单信息
            orderVo.setOrderItemVoStatisticsList(orderItemVoStatisticsList);
            orderVo.setReducePriceStatistics(reducePriceStatistics);
            orderVo.setOrderItemVoTemporaryList(orderItemVoTemporaryList);
            orderVo.setReducePriceTemporary(reducePriceTemporary);
        }
        return orderVo;
    }

    /**
     * 计算订单明细总金额
     *
     * @param orderItemVos 需要计算的订单项
     * @return
     * @throws ProjectException
     */
    @Override
    public BigDecimal reducePriceHandler(List<OrderItemVo> orderItemVos) throws ProjectException {
        return orderItemVos.stream().map(orderItemVo -> {
            BigDecimal price = orderItemVo.getPrice(); //原价
            BigDecimal reducePrice = orderItemVo.getReducePrice(); //优惠价
            Long dishNum = orderItemVo.getDishNum(); //数量
            //如果有优惠价格以优惠价格计算
            if (EmptyUtil.isNullOrEmpty(reducePrice)) {
                return price.multiply(new BigDecimal(dishNum));
            } else {
                return reducePrice.multiply(new BigDecimal(dishNum));
            }
        }).reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    @Override
    public DishVo findDishVoById(Long dishId) throws ProjectException {
        try {
            //1、查询菜品,注意：菜品图片口味信息需要调用通用服务获得
            Dish dish = dishService.getById(dishId);
            DishVo dishVo = BeanConv.toBean(dish, DishVo.class);
            //2、查询菜品口味 【中间表】
            List<DishFlavor> dishFlavors = dishFlavorService.findDishFlavorByDishId(dishId);
            //3、处理菜品口味 【数字字典】
            List<String> dataKeys = dishFlavors.stream()
                    .map(DishFlavor::getDataKey)
                    .collect(Collectors.toList());
            ResponseWrap<List<DataDictVo>> result = dataDictFegin.findValueByDataKeys(new ArrayList<>(dataKeys));
            if (!EmptyUtil.isNullOrEmpty(result)) {
                dishVo.setDataDictVos(result.getDatas());
            }
            //4、处理菜品图片
            ResponseWrap<List<FileVo>> dishFileResult = fileFeign.findFileVoByBusinessId(dishVo.getId());
            if (!EmptyUtil.isNullOrEmpty(dishFileResult)) {
                dishVo.setFileVo(dishFileResult.getDatas().get(0));
            }
            return dishVo;
        } catch (Exception e) {
            log.error("查询菜品详情异常：{}", ExceptionsUtil.getStackTraceAsString(e));
            throw new ProjectException(DishEnum.SELECT_DISH_FAIL);
        }
    }

    @Override
    @Transactional
    public OrderVo opertionShoppingCart(Long dishId,
                                        Long orderNo,
                                        String dishFlavor,
                                        String opertionType) throws ProjectException {
        //1、获得订单加锁
        String keyOrder = AppletCacheConstant.ADD_TO_ORDERITEM_LOCK + orderNo;
        RLock lockOrder = redissonClient.getLock(keyOrder);
        OrderVo orderVoResult = null;
        try {
            //2、是否获得到锁
            if (lockOrder.tryLock(
                    AppletCacheConstant.REDIS_WAIT_TIME,
                    AppletCacheConstant.REDIS_LEASETIME,
                    TimeUnit.MINUTES)) {
                String keyDish = AppletCacheConstant.REPERTORY_DISH + dishId;
                RAtomicLong atomicLong = redissonClient.getAtomicLong(keyDish);
                //3.1、添加到购物车订单项
                if (opertionType.equals(ShopConstant.OPERTION_TYPE_ADD)) {
                    this.addToShoppingCart(dishId, orderNo, dishFlavor, atomicLong);
                }
                //3.2、移除购物车订单项
                if (opertionType.equals(ShopConstant.OPERTION_TYPE_REMOVE)) {
                    this.removeToShoppingCart(dishId, orderNo, atomicLong);
                }
                //4、查询当前订单信息，并且处理订单项
                orderVoResult = orderService.findOrderByOrderNo(orderNo);
                return handlerOrderVo(orderVoResult);
            }
        } catch (InterruptedException e) {
            log.error("===编辑dishId：{}，orderNo：{}进入购物车加锁失败：{}",
                    dishId, orderNo, ExceptionsUtil.getStackTraceAsString(e));
            throw new ProjectException(OpenTableEnum.TRY_LOCK_FAIL);
        } finally {
            lockOrder.unlock();
        }
        return orderVoResult;
    }

    @Value("${delay-time}")
    private Integer delayTime;
    @Autowired
    private CartSource cartSource;

    /**
     * @param dishId     菜品ID
     * @param orderNo    订单编号
     * @param atomicLong 原子计数器
     * @return
     * @description 添加购物车订单项
     */
    private void addToShoppingCart(Long dishId,
                                   Long orderNo,
                                   String dishFlavor,
                                   RAtomicLong atomicLong) {
        //1、如果库存够，redis减库存
        if (atomicLong.decrementAndGet() >= 0) {
            //2、mysql菜品表库存
            Boolean flag = dishService.updateDishNumber(-1L, dishId);
            if (!flag) {
                //减菜品库存失败，归还redis菜品库存
                atomicLong.incrementAndGet();
                throw new ProjectException(ShoppingCartEnum.UPDATE_DISHNUMBER_FAIL);
            }
            //3、查询redis缓存的购物车订单项
            //购物车使用Redis存储，Hash数据结构：主key：业务前缀+订单编号， 辅key：菜品id， value：OrderItemVo json
            //购物车不需要登录：app端不负责存储数据，存到redis
            String key = AppletCacheConstant.ORDERITEMVO_STATISTICS + orderNo;
            RMapCache<Long, OrderItemVo> orderItemVoRMap = redissonClient.getMapCache(key);
            OrderItemVo orderItemHandler = orderItemVoRMap.get(dishId);
            //4.1、如果以往购物车订单项中无则新增
            if (EmptyUtil.isNullOrEmpty(orderItemHandler)) {
                Dish dish = dishService.getById(dishId);
                List<FileVo> affixVoList = fileFeign.findFileVoByBusinessId(dish.getId()).getDatas();
                OrderVo orderVo = orderService.findOrderByOrderNo(orderNo);
                OrderItemVo orderItemVo = OrderItemVo.builder()
                        .productOrderNo(orderNo)
                        .categoryId(dish.getCategoryId())
                        .dishId(dishId)
                        .dishName(dish.getDishName())
                        .dishFlavor(dishFlavor)
                        .dishNum(1L)
                        .price(dish.getPrice())
                        .reducePrice(dish.getReducePrice())
                        .build();
                orderItemVo.setFileVo(affixVoList.get(0));
                //沿用订单中的分库键 ****分库分表***
                orderItemVo.setShardingId(orderVo.getShardingId());
                orderItemVoRMap.put(dishId, orderItemVo);
                //4.2、如果以往购物车订单项中有此菜品，则进行购物车订单项数量递增
            } else {
                orderItemHandler.setDishNum(orderItemHandler.getDishNum() + 1);
                orderItemVoRMap.put(dishId, orderItemHandler);
            }
        } else {
            //5、redis库存不足，虽然可以不处理，但建议还是做归还库存
            atomicLong.incrementAndGet();
            throw new ProjectException(ShoppingCartEnum.UNDERSTOCK);
        }

        //*** 购物车发延迟消息
        //设置购物车当前订单的菜品过期时间，防止加入购物车后不下单占用库存
        Map<String, Long> mapMessage = new HashMap<>();
        mapMessage.put("dishId", dishId);
        mapMessage.put("orderNo", orderNo);
        MqMessage message =  MqMessage.builder()
                .id(identifierGenerator.nextId(dishId).longValue())
                .produceTime(new Timestamp(new Date().getTime()))
                .conten(JSON.toJSONString(mapMessage))
                .build();
        log.info("send message {}", message);
        // x-delay设置延迟消息时间
        cartSource.cartOutput().send(MessageBuilder.withPayload(message)
                .setHeader("x-delay", delayTime).build());
    }
    /**
     * @param dishId     菜品ID
     * @param orderNo    订单编号
     * @param atomicLong 原子计数器
     * @return
     * @description 移除购物车订单项
     */
    private void removeToShoppingCart(Long dishId, Long orderNo, RAtomicLong atomicLong) {
        boolean flag = true;
        //1、菜品库存增加
        flag = dishService.updateDishNumber(1L, dishId);
        if (!flag) {
            throw new ProjectException(ShoppingCartEnum.UPDATE_DISHNUMBER_FAIL);
        }
        //2、查询redis缓存的购物车订单项
        String key = AppletCacheConstant.ORDERITEMVO_STATISTICS + orderNo;
        RMapCache<Long, OrderItemVo> orderItemVoRMap = redissonClient.getMapCache(key);
        OrderItemVo orderItemHandler = orderItemVoRMap.get(dishId);
        //3、购物车订单项存在
        if (!EmptyUtil.isNullOrEmpty(orderItemHandler)) {
            //3.1、购物车订单项的数量大于1，修改当前数量
            if (orderItemHandler.getDishNum().intValue() > 1) {
                orderItemHandler.setDishNum(orderItemHandler.getDishNum() - 1);
                orderItemVoRMap.put(dishId, orderItemHandler);
            } else {
                //3.2购物车订单项的数量等于1，则删除此订单项
                orderItemVoRMap.remove(dishId, orderItemHandler);
            }
            //4、redis菜品库存增加
            atomicLong.incrementAndGet();
        }
    }
    /***
     * @param flag 是否操作成功
     * @param orderItemVoStatisticsList 可核算订单项
     * @param orderItemVoTemporaryList 购物车订单项
     * @return 是否操作成功
     */
    private boolean intersection(boolean flag,
                                 List<OrderItemVo> orderItemVoStatisticsList,
                                 List<OrderItemVo> orderItemVoTemporaryList) {
        //1、求交集
        List<OrderItemVo> listIntersection = new ArrayList<>(orderItemVoTemporaryList);
        listIntersection.retainAll(orderItemVoStatisticsList);
        if (!EmptyUtil.isNullOrEmpty(listIntersection)) {
            // 交集中的订单项 转map ==> key: dishId    value: num
            Map<Long, Long> dishIdAndNum = listIntersection.stream()
                                            .collect(
                                                    Collectors.toMap(OrderItemVo::getDishId,
                                                            OrderItemVo::getDishNum));

            // 在mysql的订单项中，找到交集中所包含菜品的订单项
            List<OrderItem> orderItemList = orderItemVoStatisticsList.stream()
                    // 找到交集中所包含菜品的订单项
                    .filter(orderItemVo -> dishIdAndNum.containsKey(orderItemVo.getDishId()))
                    // 封装为OrderItem
                    .map(orderItemVo -> OrderItem.builder()
                                            .id(orderItemVo.getId())
                                            //   mysql中菜品数量  + redis中对应菜品数量
                                            .dishNum(orderItemVo.getDishNum() + dishIdAndNum.get(orderItemVo.getDishId()))
                                            .build())
                    // 收集到批量集合中 进行修改
                    .collect(Collectors.toList());
            flag = orderItemService.updateBatchById(orderItemList);
        }
        return flag;
    }
    /***
     * @param flag 是否操作成功
     * @param orderItemVoStatisticsList 可核算订单项
     * @param orderItemVoTemporaryList 购物车订单项
     * @return 是否操作成功
     */
    private Boolean difference(boolean flag,
                               List<OrderItemVo> orderItemVoStatisticsList,
                               List<OrderItemVo> orderItemVoTemporaryList) {
        //1、求差集
        List<OrderItemVo> listDifference = new ArrayList<>();
        listDifference.addAll(orderItemVoTemporaryList);
        listDifference.removeAll(orderItemVoStatisticsList);
        //2、处理订单项
        if (!EmptyUtil.isNullOrEmpty(listDifference)) {
            List<OrderItem> orderItems = BeanConv
                    .toBeanList(listDifference, OrderItem.class);
            flag = orderItemService.saveBatch(orderItems);
        }
        return flag;
    }

    /***
     * @description 计算更新订单信息
     * @param orderNo   订单编号
     * @param orderVoResult 计算结果
     * @return
     * @return: java.lang.Boolean
     */
    private Boolean calculateOrderAmount(Long orderNo, OrderVo orderVoResult) {
        //1、计算订单金额
        List<OrderItem> orderItemListResult = orderItemService
                .findOrderItemByOrderNo(orderNo);
        BigDecimal sumPrice = orderItemListResult.stream()
                .map(n -> {
                            BigDecimal price = n.getPrice();
                            BigDecimal reducePrice = n.getReducePrice();
                            Long dishNum = n.getDishNum();
                            //如果有优惠价格以优惠价格计算
                            if (EmptyUtil.isNullOrEmpty(reducePrice)) {
                                return price.multiply(new BigDecimal(dishNum));
                            } else {
                                return reducePrice.multiply(new BigDecimal(dishNum));
                            }
                        }
                ).reduce(BigDecimal.ZERO, BigDecimal::add);
        //2、更新订单金额信息
        orderVoResult.setPayableAmountSum(sumPrice);
        OrderVo orderVoHandler = OrderVo.builder()
                .id(orderVoResult.getId())
                .payableAmountSum(sumPrice)
                .build();
        return orderService.updateById(BeanConv.toBean(orderVoHandler, Order.class));
    }
    @Override
    @Transactional
    public OrderVo placeOrder(Long orderNo) throws ProjectException {
        //1、锁定订单
        String key = AppletCacheConstant.ADD_TO_ORDERITEM_LOCK + orderNo;
        RLock lock = redissonClient.getLock(key);
        ProjectException projectException = null;
        OrderVo orderVoResult = null;
        try {
            if (lock.tryLock(
                    AppletCacheConstant.REDIS_WAIT_TIME,
                    AppletCacheConstant.REDIS_LEASETIME,
                    TimeUnit.MINUTES)) {
                boolean flag = true;
                // 订单必须是待付款或付款中的
                orderVoResult = orderService.findOrderByOrderNo(orderNo);
                if (orderVoResult == null) {
                    throw new ProjectException(OrderItemEnum.UPDATE_ORDERITEM_FAIL);
                }
                //2、查询可以核算订单项  mysql
                List<OrderItem> orderItemList = orderItemService.findOrderItemByOrderNo(orderVoResult.getOrderNo());
                List<OrderItemVo> orderItemVoStatisticsList = BeanConv.toBeanList(orderItemList, OrderItemVo.class);
                //2.1、处理空集合
                if (EmptyUtil.isNullOrEmpty(orderItemVoStatisticsList)) {
                    orderItemVoStatisticsList = new ArrayList<>();
                }
                //3、查询购物车订单项 redis
                key = AppletCacheConstant.ORDERITEMVO_STATISTICS + orderNo;
                RMapCache<Long, OrderItemVo> orderItemVoRMap = redissonClient.getMapCache(key);
                List<OrderItemVo> orderItemVoTemporaryList = (List<OrderItemVo>) orderItemVoRMap.readAllValues();
                //4、购物车订单项不为空才合并: 冒泡排序
                if (!EmptyUtil.isNullOrEmpty(orderItemVoTemporaryList)) {
                    // 构建合并后的订单项
                    List<OrderItemVo> dbOrderItemVos = new ArrayList<>();
                    for (OrderItemVo orderItemVo : orderItemVoTemporaryList) { // redis
                        boolean opertion = false; // 有可能 购物车中新增的菜品  在mysql的核算订单项中没有   使用: opertion做标记
                        if (!EmptyUtil.isNullOrEmpty(orderItemVoStatisticsList)) {
                            for (OrderItemVo dborderItemVo : orderItemVoStatisticsList) { // mysql
                                if (dborderItemVo.getDishId().equals(orderItemVo.getDishId())) { // 口味
                                    // 数量
                                    dborderItemVo.setDishNum(orderItemVo.getDishNum() + dborderItemVo.getDishNum());
                                    // 解决并发修改异常问题
                                    dbOrderItemVos.add(dborderItemVo);
                                    opertion = true;
                                    break;
                                }
                            }
                        }
                        if (!opertion) {
                            dbOrderItemVos.add(orderItemVo);
                        }
                    }
                    //5、执行保存或更新操作
                    List<OrderItem> orderItems = BeanConv
                            .toBeanList(dbOrderItemVos, OrderItem.class);
                    flag = orderItemService.saveOrUpdateBatch(orderItems);
                    if (!flag) {
                        projectException = new ProjectException(OrderItemEnum.UPDATE_ORDERITEM_FAIL);
                    }
                    //6、计算订单金额
                    List<OrderItem> orderItemListResult = orderItemService
                            .findOrderItemByOrderNo(orderNo);
                    BigDecimal sumPrice = orderItemListResult.stream()
                            .map(n -> {
                                        BigDecimal price = n.getPrice();
                                        BigDecimal reducePrice = n.getReducePrice();
                                        Long dishNum = n.getDishNum();
                                        //如果有优惠价格以优惠价格计算
                                        if (EmptyUtil.isNullOrEmpty(reducePrice)) {
                                            return price.multiply(new BigDecimal(dishNum));
                                        } else {
                                            return reducePrice.multiply(new BigDecimal(dishNum));
                                        }
                                    }
                            ).reduce(BigDecimal.ZERO, BigDecimal::add)
                            .setScale(2, BigDecimal.ROUND_HALF_UP);
                    //7、更新订单金额信息
                    Order order = Order.builder()
                            .id(orderVoResult.getId())
                            .payableAmountSum(sumPrice)
                            .build();
                    flag = orderService.updateById(order);
                    if (!flag) {
                        projectException = new ProjectException(OrderItemEnum.SAVE_ORDER_FAIL);
                    }
                    //8、清理redis购物车订单项目
                    orderItemVoTemporaryList.forEach(n -> {
                        orderItemVoRMap.remove(n.getDishId());
                    });
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //9、释放锁
            lock.unlock();
        }
        if (!EmptyUtil.isNullOrEmpty(projectException)) {
            throw projectException;
        }
        //10、再次最新查询订单数据
        return handlerOrderVo(orderVoResult);
    }
    @Transactional
    public OrderVo placeOrder2(Long orderNo) throws ProjectException {
        try {
            //1、锁定订单
            String key = AppletCacheConstant.ADD_TO_ORDERITEM_LOCK + orderNo;
            RLock lock = redissonClient.getLock(key);
            OrderVo orderVoResult = null;
            try {
                if (lock.tryLock(
                        AppletCacheConstant.REDIS_WAIT_TIME,
                        AppletCacheConstant.REDIS_LEASETIME,
                        TimeUnit.SECONDS)) {
                    Boolean flag = true;
                    orderVoResult = orderService.findOrderByOrderNo(orderNo);
                    //2、查询可以核算订单项
                    List<OrderItem> orderItemList = orderItemService.findOrderItemByOrderNo(orderVoResult.getOrderNo());
                    List<OrderItemVo> orderItemVoStatisticsList = BeanConv.toBeanList(orderItemList, OrderItemVo.class);
                    if (EmptyUtil.isNullOrEmpty(orderItemVoStatisticsList)) {
                        orderItemVoStatisticsList = new ArrayList<>();
                    }
                    //3、查询购物车订单项
                    key = AppletCacheConstant.ORDERITEMVO_STATISTICS + orderNo;
                    RMapCache<Long, OrderItemVo> orderItemVoRMap = redissonClient.getMapCache(key);
                    List<OrderItemVo> orderItemVoTemporaryList = (List<OrderItemVo>) orderItemVoRMap.readAllValues();
                    //4、购物车订单项不为空才合并
                    if (!EmptyUtil.isNullOrEmpty(orderItemVoTemporaryList)) {
                        //5、求交集:购物车订单项与核算订单项合并
                        flag = this.intersection(flag, orderItemVoStatisticsList, orderItemVoTemporaryList);
                        if (!flag) {
                            throw new ProjectException(OrderItemEnum.UPDATE_ORDERITEM_FAIL);
                        }
                        //6、求差集:保存购物车订单项到可核算订单项
                        flag = this.difference(flag, orderItemVoStatisticsList, orderItemVoTemporaryList);
                        if (!flag) {
                            throw new ProjectException(OrderItemEnum.UPDATE_ORDERITEM_FAIL);
                        }
                        //7、计算更新订单信息
                        flag = this.calculateOrderAmount(orderNo, orderVoResult);
                        if (!flag) {
                            throw new ProjectException(OrderItemEnum.SAVE_ORDER_FAIL);
                        }
                        //8、清理redis购物车订单项目
                        orderItemVoTemporaryList.forEach(n -> {
                            orderItemVoRMap.remove(n.getDishId());
                        });
                    }
                }
            } catch (InterruptedException e) {
                log.error("合并订单出错：{}", ExceptionsUtil.getStackTraceAsString(e));
                throw new ProjectException(OrderItemEnum.LOCK_ORDER_FAIL);
            } finally {
                lock.unlock();
            }
            //9、再次查询订单
            return handlerOrderVo(orderVoResult);
        } catch (Exception e) {
            log.error("下单操作异常：{}", ExceptionsUtil.getStackTraceAsString(e));
            throw new ProjectException(OrderEnum.PLACE_ORDER_FAIL);
        }
    }
    @Override
    public Boolean clearShoppingCart(Long orderNo) throws ProjectException {
        try {
            // 获取当前订单下   redis中的购物车信息
            String key = AppletCacheConstant.ORDERITEMVO_STATISTICS + orderNo;
            RMapCache<Long, OrderItemVo> orderItemVoRMap = redissonClient.getMapCache(key);
            //清理购物车归还库存
            if (!EmptyUtil.isNullOrEmpty(orderItemVoRMap)) {
                // 获取所有购物车选项
                List<OrderItemVo> orderItemVos = (List<OrderItemVo>) orderItemVoRMap.readAllValues();
                orderItemVos.forEach(n -> {
                    // 获取该菜品redis中库存
                    String keyDish = AppletCacheConstant.REPERTORY_DISH + n.getDishId();
                    RAtomicLong atomicLong = redissonClient.getAtomicLong(keyDish);
                    // TODO 清空购物车 归还库存  为什么是库存 + 1
                    atomicLong.incrementAndGet();
                    // 而不是 库存 + 菜品数量呢
//                    atomicLong.addAndGet(n.getDishNum());
                });
            }
            orderItemVoRMap.clear();
            return true;
        } catch (Exception e) {
            log.error("操作购物车详情异常：{}", ExceptionsUtil.getStackTraceAsString(e));
            throw new ProjectException(OrderEnum.CLEAR_SHOPPING_CART_FAIL);
        }
    }


}
